﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Xylee.Common
{
    /// <summary>
    /// Utility class for switching between view portals, views 
    /// and activating based on any specific context. Use this 
    /// class for navigating forward, back and restoring view 
    /// order based on a given set of commands. This class uses 
    /// the state pattern and command pattern during its 
    /// construction and navigation. State is stored using the 
    /// persistence layer of the core library.
    /// <remarks><seealso cref="IPersistable"/></remarks>
    /// </summary>
    public class Navigation : IPersistable
    {
        private Stack<IPortal> applicationFlow;
        private Stack<IPortal> forwardFlow;
        protected IPortal currentPortal;
        protected IPortal previousPortal;
        protected IPortal nextPortal;

        /// <summary>
        /// Triggered when the current view portal has changed 
        /// either forward, back or an entirely new activated 
        /// portal in context. See <see cref="PortalChangeEventArgs"/> 
        /// for contents of the change.
        /// </summary>
        public event EventHandler<PortalChangeEventArgs> NavigateChanged;

        /// <summary>
        /// Triggered when the current view portal is attempting 
        /// to navigate in direction. If the navigation changes 
        /// the NavigateChanged event will trigger.
        /// </summary>
        public event EventHandler<NavigateAttemptArgs> NavigateAttempted;

        public Navigation()
        {
            this.applicationFlow = new Stack<IPortal>();
            this.forwardFlow = new Stack<IPortal>();
        }

        /// <summary>
        /// Activates a given uri according to the usage of the 
        /// uri. If the uri given refers to a portal within 
        /// a specified assembly, the format that should be 
        /// used is, XymQualifiedName:QualifiedPortalName.
        /// <example>Xylee.CommonPortals:Xylee.CommonPortals.Default</example>
        /// If the uri refers to an assembly that has not been loaded 
        /// into the Available portals, the assembly loader in the 
        /// ModuleLoader can be used in this method.
        /// </summary>
        /// <param name="uri">Fully qualified portal uri address, if the portal 
        /// does not yet exist from a module, it shall be downloaded.</param>
        public virtual void Activate(string uri, string portal)
        {
            // Ensure that the paramater is not null and is not empty
            if (uri == string.Empty || uri == null)
                throw new ArgumentNullException("uri");

            if (portal == string.Empty || portal == null)
                throw new ArgumentNullException("portal");

            // Check to see if the uri is properly formatted, it should have qname:namespace,portalqname
            //if(!Regex.IsMatch(uri, @"\w*:\w*"))
            //    throw new ArgumentException("The uri is not correctly qualified, please read the specifications.", "uri");

            if (XApp.Current.AvailableModules.Exists(new Predicate<Xylee.Common.Modules.Module>(delegate(Xylee.Common.Modules.Module value)
                                                     {
                                                         if (value.Uri == uri)
                                                             return true;
                                                         return false;
                                                     })))
            {
                Xylee.Common.Modules.Module module = XApp.Current.AvailableModules.Find(
                                new Predicate<Xylee.Common.Modules.Module>(
                                    delegate(Xylee.Common.Modules.Module moduleFind)
                                                {
                                                    return moduleFind.Uri == uri;
                                                }));
                foreach (string value in module.Portals)
                {
                    if (value.Contains(portal))
                    {
                        IPortal myValue = XApp.Current.AvailablePortals.Find(new Predicate<IPortal>(delegate(IPortal portalFind)
                        {
                            return portalFind.GetType().AssemblyQualifiedName == value;
                        }));
                        if (myValue != null)
                            Activate(myValue);
                        else
                        {
                            IPortal portalValue = Construct(Type.GetType(value));
                            XApp.Current.AvailablePortals.Add(portalValue);
                            Activate(portalValue);
                        }
                        return;
                    }
                }
            }

            if (NavigateAttempted != null)
                NavigateAttempted(this, new NavigateAttemptArgs(uri, portal));
        }

        private IPortal Construct(Type t)
        {
            return (IPortal)t.GetConstructor(new Type[] { }).Invoke(new object[] { });
        }

        /// <summary>
        /// Uses the instance of the application work flow 
        /// to push the new portal into view. This will make 
        /// sure that the view does not already exist in another 
        /// partial view. It will bring it into immediate focus.
        /// </summary>
        /// <param name="portal"><seealso cref="IPortal"/> to bring in focus.</param>
        public virtual void Activate(IPortal portal)
        {
            // Ensure that the parameter is not null.
            if (null == portal)
                throw new ArgumentNullException("portal");

            // Determine if the portal already exists in some view
            bool exists = false;
            foreach (IPortal item in applicationFlow)
            {
                // Use the identifier to determine if this is the portal
                if (item.GetType().AssemblyQualifiedName == portal.GetType().AssemblyQualifiedName)
                {
                    exists = true;
                    break;
                }
            }

            // Iterate through the application flow to pop anything off of the 
            // list until a match is found according to the id. This will 
            // cause the current index in the flow to shift allowing the 
            // portals to be moving forward into the alternate flow.
            if (exists)
            {
                while (applicationFlow.Peek().GetType().AssemblyQualifiedName != portal.GetType().AssemblyQualifiedName)
                    forwardFlow.Push(applicationFlow.Pop());
            }
            else
            {
                // If the id was not found then we are activating a new portal
                // clear the forward layer, and push the current portal back
                forwardFlow.Clear();
                if (currentPortal != null && !typeof(IPortalDialog).IsAssignableFrom(currentPortal.GetType()))
                    applicationFlow.Push(currentPortal);
                // Then push the new portal before activating it
                applicationFlow.Push(portal);
            }

            // Activate the portal by setting the CurrentPortal
            CurrentPortal = applicationFlow.Pop();
        }

        /// <summary>
        /// Displays the last portal available by using the 
        /// last known display item in the application flow. 
        /// This will force the forward flow to increase by 
        /// pushing the current view portal in the stack.
        /// <remarks>If there is no available previous 
        /// portal to view, throw a navigation exception 
        /// that pertains to attempting to navigate back to 
        /// a view that does not exist.</remarks>
        /// </summary>
        public virtual void ShowLastPortal()
        {
            // Ensure that there is an available portal to navigate back to.
            if (applicationFlow.Count == 0)
                throw new NavigationUnavailableException(NavigateType.Back, "Can not attempt to navigate back " +
                      "when there is no view portal available in the stack." +
                      " Use HasPrevious() to make sure there is an available portal to go to.");

            // Push the current portal forward on the stack, then set the current portal
            // to the previous portal in the application flow
            if (currentPortal != null && !typeof(IPortalDialog).IsAssignableFrom(currentPortal.GetType()))
                forwardFlow.Push(currentPortal);
            CurrentPortal = applicationFlow.Pop();
        }

        /// <summary>
        /// Displays the next portal available by using the 
        /// next known display item in the forward application 
        /// stack. This will force the application flow stack 
        /// to increase by pushing the current portal in the stack.
        /// <remarks>If there is no available portal to switch to 
        /// in the forward stack, throw a navigation exception 
        /// that pertains to attempting to navigate forward to 
        /// a view that does not exist.</remarks>
        /// </summary>
        public virtual void ShowNextPortal()
        {
            // Ensure that there is an available portal to navigate to
            if (forwardFlow.Count == 0)
                throw new NavigationUnavailableException(NavigateType.Forward, "Can not attempt to navigate forward " +
                          "when there is no view portal available in the stack." +
                          " Use HasNext() to make sure there is an available portal to go to.");

            // Push the current portal back on the stack, then set the current portal 
            // to the next portal in the forward flow stack.
            if (currentPortal != null && !typeof(IPortalDialog).IsAssignableFrom(currentPortal.GetType()))
                applicationFlow.Push(currentPortal);
            CurrentPortal = forwardFlow.Pop();
        }

        /// <summary>
        /// Checks the forward flow stack to see 
        /// if there is an available portal to 
        /// transition forward to.
        /// </summary>
        /// <returns>True if there is an available portal, false otherwise.</returns>
        public virtual bool HasNext()
        {
            return forwardFlow.Count != 0;
        }

        /// <summary>
        /// Checks the application flow stack to see 
        /// if there is an available portal to 
        /// navigate back to.
        /// </summary>
        /// <returns>True if there is an available portal, false otherwise.</returns>
        public virtual bool HasPrevious()
        {
            return applicationFlow.Count != 0;
        }

        /// <summary>
        /// Returns or sets the currently visible portal 
        /// in the application. By setting this it will 
        /// cause a portal change event to trigger.
        /// </summary>
        public IPortal CurrentPortal
        {
            get { return this.currentPortal; }
            set
            {
                if (currentPortal != null)
                {
                    currentPortal.ClearLayout();
                    if (currentPortal.GetType().AssemblyQualifiedName != value.GetType().AssemblyQualifiedName)
                    {
                        if (NavigateChanged != null)
                            this.NavigateChanged(this, new PortalChangeEventArgs(currentPortal, value));
                        currentPortal = value;
                    }
                }
                else
                {
                    if (NavigateChanged != null)
                        this.NavigateChanged(this, new PortalChangeEventArgs(currentPortal, value));
                    currentPortal = value;
                }

                currentPortal.SetupDefaultLayout();
            }
        }

        public IPortal PreviousPortal
        {
            get
            {
                if (this.HasPrevious())
                    return applicationFlow.Peek();
                else
                    throw new NavigationUnavailableException(NavigateType.Back, "Can not attempt to navigate back " +
                              "when there is no view portal available in the stack." +
                              " Use HasPrevious() to make sure there is an available portal to go to.");
            }
        }

        public IPortal NextPortal
        {
            get
            {
                if (this.HasNext())
                    return forwardFlow.Peek();
                else
                    throw new NavigationUnavailableException(NavigateType.Forward, "Can not attempt to navigate forward " +
                              "when there is no view portal available in the stack." +
                              " Use HasNext() to make sure there is an available portal to go to.");
            }
        }
    }
}
